package display;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import model.Buffer;
import model.FloatRegister;
import model.Instruction;
import model.IntegerRegister;
import model.Memory;
import model.ReservationStation;
import controller.Control;

public class SimulatorFrame extends JFrame implements ActionListener {
	
	public enum Operation {
		LoadCode, LoadMem, SaveMem, LoadReg, SaveReg, Exit, 
		SeqRun, StepRun, Stop, Help, Info
	};
	
	private HashMap<JComponent, Operation> mapper = null;
	
	public JTable instQueue = null;
	public JTable loadQueue = null;
	public JTable storeQueue = null;
	public JTable mem = null;
	public JTable resvStat = null;
	public JTable fReg = null;
	public JTable iReg = null;
	
	public JScrollPane instPane = null;
	
	public JLabel clockInfoLabel = null;
	public JLabel clockLabel = null;
	public JLabel instCountLabel = null;
	public JLabel instCountInfoLabel = null;
	public JLabel pcLabel = null;
	public JLabel pcInfoLabel = null;
	
	private Control control = null;
	
	public SimulatorFrame() {
		super();
		
		this.setLayout(null);
		initMenuBar();
		initListener();
		initTables();
		updateClock(0);
		updateInstCount(0);
		updatePC(0);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setSize(800, 600);
		this.setVisible(true);
		this.setResizable(false);
		this.setTitle("Tomasulo Simulator");
	}
	
	private void error(String message) {
		JOptionPane.showMessageDialog(this, message);
	}
	
	public void setControl(Control c) {
		this.control = c;
		if (control == null) {
			error("Control is null!");
		}
	}
	
	public void initListener() {
		for (JComponent c : mapper.keySet()) {
			JMenuItem item = (JMenuItem) c;
			item.addActionListener(this);
		}
	}
	
	public void update() {
		if (control != null) {
			this.updateFReg();
			this.updateIReg();
			this.updateInstructions();
			this.updateStoreQueue();
			this.updateLoadQueue();
			this.updateMem();
			this.updateReservationStations();
			this.updatePC(control.getPC());
			this.updateClock(control.getClock());
			this.updateInstCount(control.getInstructionSize());
//			this.repaint();
		} else {
			error("Control is null!");
		}
	}
	
	private void updateMem() {
		TableModel model = mem.getModel();
		List<Memory> memList = control.getMemoryList();
		for (int i = 0; i < mem.getColumnCount(); i++) {
			// to be continued
			Integer addr = null;
			try {
				addr = Integer.parseInt((String) model.getValueAt(0, i), 16) / 4;
			} catch (NumberFormatException e) {
				addr = i;
				model.setValueAt(Integer.toHexString(i * 4), 0, i);
			}
			Float value = memList.get(addr).data;
			model.setValueAt(value.toString(), 1, i);
		}
	}
	
	private void updateFReg() {
		/*
		 * FloatRegister.id is useless. the rank comes from list rank.
		 * The same with IntegerRegister.
		 */
		List<FloatRegister> frlist = control.getFloatRegisterList();
		if (frlist.size() != 11) {
			error("Float register list size error!");
			return;
		}
		TableModel model = fReg.getModel();
		for (int i = 0; i < frlist.size(); i++) {
			model.setValueAt(frlist.get(i).flag, 0, i);
			model.setValueAt(frlist.get(i).data.toString(), 1, i);
		}
	}
	
	private void updateIReg() {
		List<IntegerRegister> irlist = control.getIntegerRegisterList();
		if (irlist.size() != 11) {
			error("Float register list size error!");
			return;
		}
		TableModel model = iReg.getModel();
		for (int i = 0; i < irlist.size(); i++) {
			model.setValueAt(irlist.get(i).data.toString(), 0, i);
		}
	}
	
	private void updateReservationStations() {
		/*
		 * If ReservationStations.isBusy == false, then only
		 * show isBusy and name.
		 */
		List<ReservationStation> addRsList = control.getAddRSList();
		int offset = addRsList.size();
		TableModel model = resvStat.getModel();
		for (int i = 0; i < offset; i++) {
			ReservationStation rs = addRsList.get(i);
			for (int j = 0; j < model.getColumnCount(); j++) {
				model.setValueAt("", i, j);
			}
			if (rs.isBusy) {
				model.setValueAt(rs.time.toString(), i, 0);
				model.setValueAt(rs.name, i, 1);
				model.setValueAt("YES", i, 2);
				model.setValueAt(rs.op, i, 3);
				model.setValueAt(rs.vi.toString(), i, 4);
				model.setValueAt(rs.vk.toString(), i, 5);
				model.setValueAt(rs.qi, i, 6);
				model.setValueAt(rs.qk, i, 7);
			} else {
				model.setValueAt(rs.name, i, 1);
				model.setValueAt("NO", i, 2);
			}
		}
		
		List<ReservationStation> multRsList = control.getMultRSList();
		for (int i = offset; i < offset + multRsList.size(); i++) {
			ReservationStation rs = multRsList.get(i - offset);
			for (int j = 0; j < model.getColumnCount(); j++) {
				model.setValueAt("", i, j);
			}
			if (rs.isBusy) {
				model.setValueAt(rs.time.toString(), i, 0);
				model.setValueAt(rs.name, i, 1);
				model.setValueAt("YES", i, 2);
				model.setValueAt(rs.op, i, 3);
				model.setValueAt(rs.vi.toString(), i, 4);
				model.setValueAt(rs.vk.toString(), i, 5);
				model.setValueAt(rs.qi, i, 6);
				model.setValueAt(rs.qk, i, 7);
			} else {
				model.setValueAt(rs.name, i, 1);
				model.setValueAt("NO", i, 2);
			}
		}
	}
	
	private void updateLoadQueue() {
		/*
		 * If Buffer.qi == Control.READY, then assign vi to Cache;
		 * otherwise assign Cache null. The same with updateStoreQueue.
		 */
		List<Buffer> bufferList = control.getLoadBufferList();
		TableModel model = loadQueue.getModel();
		for (int i = 0; i < bufferList.size(); i++) {
			Buffer buffer = bufferList.get(i);
			model.setValueAt(buffer.time.toString(), i, 0);
			if (buffer.isBusy) {
				model.setValueAt("YES", i, 1);
				model.setValueAt(buffer.vi.toString(), i, 3);
			} else {
				model.setValueAt("NO", i, 1);
				model.setValueAt("", i, 3);
			}
			model.setValueAt(buffer.address.toString(), i, 2);
		}
	}
	
	private void updateStoreQueue() {
		List<Buffer> bufferList = control.getStoreBufferList();
		TableModel model = storeQueue.getModel();
		for (int i = 0; i < bufferList.size(); i++) {
			Buffer buffer = bufferList.get(i);
			model.setValueAt(buffer.time.toString(), i, 0);
			if (buffer.isBusy) {
				model.setValueAt("YES", i, 1);
				model.setValueAt(buffer.vi.toString(), i, 3);
			} else {
				model.setValueAt("NO", i, 1);
				model.setValueAt("", i, 3);
			}
			model.setValueAt(buffer.address.toString(), i, 2);
		}
	}
	
	private void updateInstructions() {
		List<Instruction> instList = control.getInstructionList();
		if (instList == null) {
			error("instList is null");
			return;
		}
		if (instQueue.getRowCount() < instList.size()) {
			int height = instQueue.getPreferredSize().height;
			instQueue = new JTable(new String[instList.size()][instColumns.length], instColumns);
			instPane.getViewport().removeAll();
			instPane.getViewport().add(instQueue);
			this.repaint();
		}
		for (int i = 0; i < instList.size(); i++) {
			Instruction inst = instList.get(i);
			instQueue.setValueAt(inst.op, i, 0);
			switch (inst.op) {
			case Control.ADDD:
			case Control.DIVD:
			case Control.SUBD:
			case Control.MULD:
				instQueue.setValueAt("F" + inst.desti, i, 1);
				instQueue.setValueAt("F" + inst.sourcej, i, 2);
				instQueue.setValueAt("F" + inst.sourcek, i, 3);
				break;
			case Control.LD:
			case Control.ST:
				instQueue.setValueAt("F" + inst.desti, i, 1);
				instQueue.setValueAt(inst.sourcej.toString(), i, 2);
				instQueue.setValueAt("R" + inst.sourcek, i, 3);
				break;
			}
			if (inst.isLaunch) {
				instQueue.setValueAt("YES", i, 4);
			} else {
				instQueue.setValueAt("NO", i, 4);
			}
			if (inst.isExecute) {
				instQueue.setValueAt("YES", i, 5);
			} else {
				instQueue.setValueAt("NO", i, 5);
			}
			if (inst.isWriteBack) {
				instQueue.setValueAt("YES", i, 6);
			} else {
				instQueue.setValueAt("NO", i, 6);
			}
		}
		for (int i = instList.size(); i < instQueue.getRowCount(); i++) {
			for (int j = 0; j < instQueue.getColumnCount(); j++) {
				instQueue.setValueAt("", i, j);
			}
		}
		instQueue.repaint();
	}
	
	private void updatePC(int PC) {
		if (pcInfoLabel == null) {
			pcInfoLabel = new JLabel("PC:");
			pcInfoLabel.setFont(new Font("Dialog", 1, 30));
			pcInfoLabel.setBounds(20, 330, 200, 50);
			this.getContentPane().add(pcInfoLabel);
		}
		if (pcLabel == null) {
			pcLabel = new JLabel();
			this.getContentPane().add(pcLabel);
			pcLabel.setFont(new Font("Dialog", 1, 30));
			pcLabel.setBounds(150, 330, 100, 50);
		}
		pcLabel.setText("" + PC);
	}
	
	private void updateInstCount(int count) {
		if (instCountInfoLabel == null) {
			instCountInfoLabel = new JLabel("指令数：");
			instCountInfoLabel.setFont(new Font("Dialog", 1, 30));
			instCountInfoLabel.setBounds(20, 290, 200, 50);
			this.getContentPane().add(instCountInfoLabel);
		}
		if (instCountLabel == null) {
			instCountLabel = new JLabel();
			this.getContentPane().add(instCountLabel);
			instCountLabel.setFont(new Font("Dialog", 1, 30));
			instCountLabel.setBounds(150, 290, 100, 50);
		}
		instCountLabel.setText("" + count);
	}
	
	
	private void updateClock(int clock) {
		if (clockInfoLabel == null) {
			clockInfoLabel = new JLabel("Clock:");
			clockInfoLabel.setFont(new Font("Dialog", 1, 30));
			clockInfoLabel.setBounds(20, 250, 200, 50);
			this.getContentPane().add(clockInfoLabel);
		}
		if (clockLabel == null) {
			clockLabel = new JLabel();
			this.getContentPane().add(clockLabel);
			clockLabel.setFont(new Font("Dialog", 1, 30));
			clockLabel.setBounds(150, 250, 100, 50);
		}
		clockLabel.setText("" + clock);
	}
	
	String[] instColumns = {
			"Name", "Desti", "Sourcej", "Sourcek", 
			"发射指令", "执行", "写回结果"
	};
	String[] loadColumns = {
			"Time", "Busy", "Address", "Cache"
	};
	String[] storeColumns = {
			"Time", "Busy", "Address", "Qi"
	};
	String[] resvColumns = {
			"Time", "Name", "Busy", "Op", "Vi", "Vk", "Qi", "Qk"
	};
	String[] fRegColumns = {
			"F0", "F1", "F2", "F3", "F4", "F5", "F6", 
			"F7", "F8", "F9", "F10",
	};
	String[] iRegColumns = {
			"R0", "R1", "R2", "R3", "R4", "R5", "R6", 
			"R7", "R8", "R9", "R10",
	};
	
	public static int width = 60;
	
	public void addTable(JTable table, int x, int y) {
//		JScrollPane pane = new JScrollPane(table);
		JScrollPane pane = new JScrollPane();
		if (table.equals(this.instQueue)) {
			this.instPane = pane;
			pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
			pane.setAutoscrolls(true);
		}
		pane.getViewport().add(table);
		pane.setAutoscrolls(true);
		Dimension size = table.getPreferredSize();
		size.width = width * table.getColumnCount();
		table.setEnabled(false);
		table.setDragEnabled(false);
		int delta = 3 + table.getTableHeader().getPreferredSize().height;
		pane.setBounds(x, y, size.width, size.height + delta);
		this.getContentPane().add(pane);
		this.repaint();
	}
	
	public void addLabel(String content, int x, int y) {
		JLabel l = new JLabel(content);
		Dimension size = l.getPreferredSize();
		l.setBounds(x, y, size.width, size.height);
		this.getContentPane().add(l);
	}
	
	public void initTables() {
		instQueue = new JTable(new String[6][instColumns.length], instColumns);
		addTable(instQueue, 30, 30);
		addLabel("指令队列及运行状态", 200, 10);
		
		loadQueue = new JTable(new String[3][loadColumns.length], loadColumns);
		addTable(loadQueue, 530, 30);
		addLabel("Load Queue", 600, 10);
		addLabel("Load 0", 490, 50);
		addLabel("Load 1", 490, 65);
		addLabel("Load 2", 490, 80);
		
		storeQueue = new JTable(new String[3][storeColumns.length], storeColumns);
		addTable(storeQueue, 530, 150);
		addLabel("Store Queue", 600, 130);
		addLabel("Store 0", 485, 170);
		addLabel("Store 1", 485, 185);
		addLabel("Store 2", 485, 200);
		
		mem = new JTable(2, 5);
		addTable(mem, 80, 180);
		addLabel("内存 Mem", 200, 160);
		addLabel("Addr", 45, 200);
		addLabel("Value", 40, 215);
		mem.setEnabled(true);
		
		resvStat = new JTable(new String[6][resvColumns.length], resvColumns);
		addTable(resvStat, 250, 250);
		addLabel("Reservation Stations", 420, 230);
		
		fReg = new JTable(new String[2][fRegColumns.length], fRegColumns);
		addTable(fReg, 100, 400);
		addLabel("浮点寄存器FU", 400, 380);
		addLabel("表达式", 50, 420);
		addLabel("数据", 50, 435);
		
		iReg = new JTable(new String[1][iRegColumns.length], iRegColumns);
		addTable(iReg, 100, 500);
		addLabel("整型寄存器RU", 400, 480);
		addLabel("数据", 50, 520);
	}
	
	public void initMenuBar() {
		if (mapper == null) {
			mapper = new HashMap<JComponent, Operation>();
		}
		JMenuBar menubar = new JMenuBar();
		JMenu file = new JMenu("文件");
		JMenu run = new JMenu("运行");
		JMenu help = new JMenu("帮助");
		
		JMenuItem loadCode = new JMenuItem("载入源代码");
		mapper.put(loadCode, Operation.LoadCode);
		file.add(loadCode);
		
		file.addSeparator();
		
		JMenuItem loadMem = new JMenuItem("载入内存");
		mapper.put(loadMem, Operation.LoadMem);
		file.add(loadMem);
		
		JMenuItem saveMem = new JMenuItem("保存内存");
		mapper.put(saveMem, Operation.SaveMem);
		file.add(saveMem);
		
		file.addSeparator();
		
		JMenuItem loadReg = new JMenuItem("载入寄存器");
		mapper.put(loadReg, Operation.LoadReg);
		file.add(loadReg);
		
		JMenuItem saveReg = new JMenuItem("保存寄存器");
		mapper.put(saveReg, Operation.SaveReg);
		file.add(saveReg);
		
		file.addSeparator();
		
		JMenuItem exit = new JMenuItem("退出");
		mapper.put(exit, Operation.Exit);
		file.add(exit);
		
		
		JMenuItem stepRun = new JMenuItem("单步运行");
		stepRun.setAccelerator(KeyStroke.getKeyStroke(' '));
		mapper.put(stepRun, Operation.StepRun);
		run.add(stepRun);
		
		JMenuItem seqRun = new JMenuItem("连续运行");
		mapper.put(seqRun, Operation.SeqRun);
		run.add(seqRun);
		
		JMenuItem stop = new JMenuItem("停止运行");
		mapper.put(stop, Operation.Stop);
		run.add(stop);
		
		JMenuItem readme = new JMenuItem("使用帮助");
		mapper.put(readme, Operation.Help);
		help.add(readme);
		
		JMenuItem info = new JMenuItem("关于我们");
		mapper.put(info, Operation.Info);
		help.add(info);
		
		menubar.add(file);
		menubar.add(run);
//		menubar.add(help);
		
		this.setJMenuBar(menubar);
	}
	
	public void initToolBar() {
		JToolBar toolbar = new JToolBar();
		JButton step = new JButton("单步运行");
		toolbar.add(step);
//		this.add(toolbar);
		this.add(toolbar, BorderLayout.PAGE_START);
	}

	private String prevDir = null;
	
	@Override
	public void actionPerformed(ActionEvent arg0) {
		// TODO Auto-generated method stub
		JMenuItem item = (JMenuItem) arg0.getSource();
		if (!mapper.containsKey(item)) {
			return;
		}
		Operation op = mapper.get(item);
		JFileChooser chooser = new JFileChooser(prevDir);
		String path;
		int flag;
		
		if (control == null) {
			error("Control is null!");
			return;
		}
		
		switch (op) {
		case LoadCode:
			path = "";
			flag = chooser.showOpenDialog(this);
			if (flag == JFileChooser.APPROVE_OPTION) {
				path = chooser.getSelectedFile().getAbsolutePath();
				prevDir = path;
				control.loadInstruction(path);
			}
			break;
		case LoadMem:
			path = "";
			flag = chooser.showOpenDialog(this);
			if (flag == JFileChooser.APPROVE_OPTION) {
				path = chooser.getSelectedFile().getAbsolutePath();
				prevDir = path;
				control.loadMemory(chooser.getSelectedFile().getAbsolutePath());
			}
			break;
		case SaveMem:
			flag = chooser.showSaveDialog(this);
			if (flag == JFileChooser.APPROVE_OPTION) {
				path = chooser.getSelectedFile().getAbsolutePath();
				prevDir = path;
				control.saveMemory(path);
			}
			break;
		case LoadReg:
			path = "";
			flag = chooser.showOpenDialog(this);
			if (flag == JFileChooser.APPROVE_OPTION) {
				path = chooser.getSelectedFile().getAbsolutePath();
				prevDir = path;
				control.loadIntegerReg(chooser.getSelectedFile().getAbsolutePath());
			}
			break;
		case SaveReg:
			flag = chooser.showSaveDialog(this);
			if (flag == JFileChooser.APPROVE_OPTION) {
				path = chooser.getSelectedFile().getAbsolutePath();
				prevDir = path;
				control.saveIntegerReg(path);
			}
			break; 
		case SeqRun:
			String interval = JOptionPane.showInputDialog(this, "Please input the interval (in ms)");
			System.out.println(interval);
			control.setTimeSlice(Integer.parseInt(interval));
			control.autoMode();
			
			break;
		case StepRun:
			control.update();
			break;
		case Stop:
			control.manualMode();
			break;
		case Help:
			break;
		case Info:
			break;
		case Exit:
			this.dispose();
			System.exit(0);
			return;
		default:
			JOptionPane.showMessageDialog(this, "Something goes wrong...");
		}
		
	}

	
}
